﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Security.Cryptography;

namespace generator_one
{
    class Program
    {
        #region base types

        public class Template
        {
            public string Name { get; set; }

            public string Description { get; set; }

            public string Value { get; set; }

            public override string ToString()
            {
                return string.Format("T: {0}\t - \tD: {1}\r\n{2}",
                    Name,
                    Description,
                    Value);
            }
        }

        public class Rule
        {
            public string Name { get; set; }

            public string Expression { get; set; }

            public bool Live { get; set; }

            public override string ToString()
            {
                return string.Format("T: {0}\t - \tE: {1}\t - \tL: {2}",
                    Name,
                    Expression,
                    Live);
            }
        }

        public class Type
        {
            public string Name { get; set; }

            public string Default { get; set; }

            public List<Rule> Rules { get; set; }

            public Type()
            {
                Rules = new List<Rule>();
            }

            public override string ToString()
            {
                return string.Format("T: {0}\t - \tD: {1}\t - \tR: {2}",
                    Name,
                    Default,
                    Rules != null && Rules.Count > 0 ?
                    (from x in Rules
                     select x.Name).Aggregate((a, b) => a + ", " + b) :
                     string.Empty);
            }
        }

        public class Field
        {
            public string Name { get; set; }

            public string Description { get; set; }

            public Type Type { get; set; }

            public List<string> Rules { get; set; }

            public Field()
            {
                Rules = new List<string>();
            }

            public override string ToString()
            {
                return string.Format("\tF: {0}\t - \tT: {1}\t - \tR: {2} - \tD: {3}",
                    Name,
                    Type.Name,
                    Rules != null && Rules.Count > 0 ?
                    (from x in Rules
                     select x).Aggregate((a, b) => a + ", " + b) :
                     string.Empty,
                     Description);
            }
        }

        public class Domain
        {
            public string Name { get; set; }

            public string Description { get; set; }

            public List<Field> Fields { get; set; }

            public List<string> Search { get; set; }

            public Domain()
            {
                Fields = new List<Field>();
            }

            public override string ToString()
            {
                return string.Format("D: {0} - \tD: {1}\n{2}",
                    Name,
                    Description,
                    Fields != null && Fields.Count > 0 ?
                    (from x in Fields
                     select x.ToString()).Aggregate((a, b) => a + "\n" + b) : string.Empty);
            }
        }

        #endregion

        static List<Template> templates = null;

        static List<Domain> domains = null;

        static void Main(string[] args)
        {
            string input = File.ReadAllText(args[0]);

            input = removeComents(input);

            input = LoadTemplates(input);

            List<string> strings = getStrings(input);

            input = removeStrings(strings, input);

            LoadDomains2(strings, input);

            Console.ReadKey();
        }

        private static List<Domain> LoadDomains2(List<string> strings, string input)
        {
            List<Domain> domains = new List<Domain>();

            List<Type> types = new List<Type>();

            List<Rule> rules = new List<Rule>();

            List<string> passd_types = new List<string>();

            types.AddRange( new []{
                new Type { Name = "type" },
                new Type { Name = "rule" }        
            });

            int last_knownTypes = types.Count;

            Domain lastDomain = null;

            Regex r = new Regex(
                                @"(?<type_name>\w+?)\ (?<type>\w+)
                                (?:
                                (?:\ ?\((?:(?<type_params>\w+)(?:\)|,\ ))+)?
                                (?:\ ?/(?:(?<regex>[^/]+)(?:/))+?)?
                                (?:\ ?(?<type_description>\w+))?
                                )*",
                            RegexOptions.IgnorePatternWhitespace
                            | RegexOptions.IgnoreCase
                            | RegexOptions.Compiled);

            MatchCollection mm = r.Matches(input);

            lastDomain = null;

            foreach (Match m in mm)
            {
                if(m.Groups["type"].Value == "rule")
                {
                    rules.Add(CreateRule(m));

                    continue;
                }

                if (lastDomain == null && m.Groups["type"].Value != "domain")
                {
                    types.Add(CreateType(types, rules, m));

                    continue;
                }

                if (m.Groups["type"].Value == "domain")
                {
                    lastDomain = (from x in domains
                                        where x.Name == m.Groups["type_name"].Value
                                        select x).FirstOrDefault();

                    if (lastDomain == null)
                    {
                        lastDomain = CreateDomain(strings, types, m);

                        domains.Add(lastDomain);                            

                        continue;
                    }

                    lastDomain.Fields.Add(CreateField(strings, types, m));

                    continue;
                }

                if (lastDomain != null)
                {
                    lastDomain.Fields.Add(CreateField(strings, types, m));
                }
            }

             

          //  domains.ForEach(x => Console.WriteLine(x.ToString()));

            return domains;
        }

        private static Domain CreateDomain(List<string> strings, List<Type> types, Match m)
        {
            return new Domain
            {
                Name = m.Groups["type_name"].Value,

                Description = (from x in strings
                               where hash(x) == m.Groups["type_description"].Value
                               select x).FirstOrDefault(),

                Search = (from Capture x in m.Groups["type_params"].Captures
                          select x.Value).Reverse<string>().ToList()
            };
        }

        private static Field CreateField(List<string> strings, List<Type> types, Match m)
        {
            return new Field
            {
                Name = m.Groups["type_name"].Value,

                Type = (from x in types
                        where x.Name == m.Groups["type"].Value
                        select x).FirstOrDefault(),

                Rules = (from Capture x in m.Groups["type_params"].Captures
                         select x.Value).ToList(),

                Description = (from x in strings
                               where hash(x) == m.Groups["type_description"].Value
                               select x).FirstOrDefault()
            };
        }

        private static Type CreateType(List<Type> types, List<Rule> rules, Match m)
        {
            Type t = new Type
            {
                Name = m.Groups["type_name"].Value,

                //Default = m.Groups.Count > 4 ? m.Groups[4].Value : string.Empty
            };

            t.Rules.AddRange((from x in types
                              where x.Name == m.Groups["type"].Value
                              select x.Rules).FirstOrDefault());

            if (!string.IsNullOrEmpty(m.Groups["type_params"].Value))
                t.Rules.AddRange((from Capture x in m.Groups[3].Captures
                                  join Rule y in rules on x.Value equals y.Name
                                  select y));
            return t;
        }

        private static Rule CreateRule(Match m)
        {
            return new Rule
            {
                Name = m.Groups["type_name"].Value,
                Expression = m.Groups["regex"].Value
            };
        }

        private static string LoadTemplates(string input)
        {
            templates = new List<Template>();

            Regex r = new Regex(@"(?:(?<template_name>\w+?)\ )?template
                                (?:
                                (?:\ ?\((?:(?<template_params>\w+)(?:\)|,\ ))+)?
                                (?:\ ?(?<template_description>\w+))?
                                )*(?:.|\r)*?\n
                                (?<template>.*?)(?=(?:(?:\w+?)\ )?template|$)",
                    RegexOptions.Singleline
                  | RegexOptions.IgnorePatternWhitespace
                  | RegexOptions.IgnoreCase
                  | RegexOptions.Compiled);

            MatchCollection mm = r.Matches(input);

            foreach (Match m in mm)
            {
                templates.Add(new Template
                {
                    Name = m.Groups["template_name"].Value,

                    Value = m.Groups["template"].Value,

                    Description = m.Groups["template_description"].Value,
                });

                Console.WriteLine(templates[templates.Count - 1]);
            }

            return r.Replace(input, string.Empty);
        }

        static string removeComents(string input)
        {
            input = Regex.Replace(input, "//.*$", string.Empty,
                RegexOptions.Multiline);

            input = Regex.Replace(input, "/\\*.*?(\\*/|$)", string.Empty,
                RegexOptions.Singleline);

            return input;
        }

        static string removeStrings(List<string> strings, string input)
        {
            foreach (string s in strings)
                input = input.Replace(s, hash(s));

            input = Regex.Replace(input, "[\"']", string.Empty);

            return input;
        }

        static List<string> getStrings(string input)
        {
            Regex r = new Regex(@"([""'])(?<string>(?:[^\\1]|\\\1)*?)(?:\1|$)",
                    RegexOptions.Singleline
                  | RegexOptions.IgnorePatternWhitespace
                  | RegexOptions.IgnoreCase
                  | RegexOptions.Compiled);

            MatchCollection mm = r.Matches(input);

            var a = (from Match x in mm
                     group x by x.Groups["string"].Value into y
                     select y.Key).ToList();

            return a;
        }

        static string hash(string input)
        {
            // step 1, calculate MD5 hash from input
            MD5 md5 = MD5.Create();
            byte[] inputBytes = Encoding.ASCII.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }
            return sb.ToString();
        }
    }
}
